#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <ctime>

#include <pthread.h>

#include "noti.h"

#include <cairo.h>
#include <cairo-xlib.h>
#include <cairo-xlib-xrender.h>
#include <extensions/shape.h>
#include <Xlib.h>
#include <Xatom.h>

#define PI 3.1415926535

int gwidth = 200;
int gheight = 80;

__uint32_t IDCounter = 0;
int CurretHeight = 0;

class Mess_t
{
public:

  ~Mess_t()
  {

  };

  int Id;
  char * Title;
  char * Message;
  int Time;

  __uint32_t height;
  std::time_t createdTime;
  Window win; 
  cairo_surface_t *cs;
};

std::vector<Mess_t*> * messages;

pthread_mutex_t mes_mut;

void
setAtomProperty(Display * dpy,
		Window &win,
		const char * at1,
		const char * at2,
		unsigned int bits = 32);




void *
reciever(void * args)
{
  int sock, listener;
  struct sockaddr_in addr;
  char buf[1024];
  int bytes_read;

  Display *dpy = (Display*)*((void**)args);
  Window rootwin;
  Window win;
  XEvent e; 
  int scr;
  Pixmap bitmap;

  XLockDisplay(dpy);

  scr=DefaultScreen(dpy);
  rootwin=RootWindow(dpy, scr);
  
  unsigned int width = 200;
  unsigned int height = 85;
  int xof = 0;
  int yof = 0;

  int result = XReadBitmapFile(dpy, rootwin, "./raw_test_back-mask.xbm", 
  			       &width, &height, &bitmap, &xof, &yof);


  //bitmap = XCreateBitmapFromData(dpy,rootwin , fdata, width, height);
  
  switch(result)
    {
    case BitmapOpenFailed:
      printf("BitmapOpenFailed!\n");
      exit(1);
    case BitmapFileInvalid:
      printf("BitmapFileInvalid!\n");
      exit(1);
    };

  XUnlockDisplay(dpy);

  listener = socket(AF_INET, SOCK_STREAM, 0);
  if(listener < 0)
    {
      perror("socket");
      exit(1);
    }
  
  addr.sin_family = AF_INET;
  addr.sin_port = htons(3425);
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  if(bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
      perror("bind");
      exit(2);
    }
  
    listen(listener, 1);
    
    while(1)
      {
	sock = accept(listener, NULL, NULL);
	if(sock < 0)
	  {
	    perror("accept");
	    exit(3);
	  }
	
	noti::RecType rt;
	bytes_read = recv(sock, &rt, sizeof(noti::RecType), MSG_WAITALL);
	
	switch(rt)
	  {
	  case noti::Message:{
	    Mess_t * newMes = new Mess_t(); 
	    __uint32_t pc = 0;
	    bytes_read = recv(sock, &pc, sizeof(__uint32_t), MSG_WAITALL);
	    printf("Recieving %d parametres; \n",pc);
	    for(int pi = 0; pi < pc; ++pi)
	      {
		printf("Recieve paramete %d: \n",pi);
		noti::RecMessFields rmf;
		bytes_read = recv(sock, &rmf, sizeof(noti::RecMessFields), MSG_WAITALL);
		switch(rmf)
		  {
		  case noti::Title:{
		    printf("Recieving title ...\n");
		    __uint32_t slen = 0;
		    bytes_read = recv(sock, &slen, 4, MSG_WAITALL);
		    char * sbuf = new char[slen+1];
		    bytes_read = recv(sock, sbuf, slen, MSG_WAITALL);
		    printf("Recieved Title '%s' (%u)\n",sbuf,slen);
		    newMes->Title = sbuf;
		    newMes->Title[slen] = '\0';
		  }; break;
		  case noti::Body:{
		    __uint32_t slen = 0;
		    bytes_read = recv(sock, &slen, sizeof(__uint32_t), MSG_WAITALL);
		    char * sbuf = new char[slen];
		    bytes_read = recv(sock, sbuf, slen, MSG_WAITALL);
		    printf("Recieved Body '%s' (%u)\n",sbuf,slen);
		    newMes->Message = sbuf;
		    newMes->Message[slen] = '\0';
		  }; break;
		  case noti::Time:{
		    __uint32_t slen = 0;
		    bytes_read = recv(sock, &slen, sizeof(__uint32_t), MSG_WAITALL);
		    printf("Recieved Time %u\n",slen);
		    newMes->Time = slen;
		  }; break;
		    
		  }
	      };
	    printf("Title - %s; \n",newMes->Title);
	    printf("Message - %s; \n",newMes->Message);
	    printf("Time - %d; \n",newMes->Time);

	    XLockDisplay(dpy);

	    newMes->height = gheight+5;
	    newMes->createdTime = std::time(NULL);
	    newMes->win=XCreateSimpleWindow(dpy, rootwin, 1, CurretHeight, gwidth, gheight, 0, 
				    WhitePixel(dpy, scr), WhitePixel(dpy, scr));
	    
  
	    XStoreName(dpy, newMes->win, "hello");
	    XSelectInput(dpy, newMes->win, NULL/*ExposureMask*/);
	    XShapeCombineMask(dpy, newMes->win, 0, 0, 0, bitmap, 0);
  
	    setAtomProperty(dpy,newMes->win,"_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_DOCK");
	    setAtomProperty(dpy,newMes->win,"_NET_WM_ALLOWED_ACTIONS", "_NET_WM_ACTION_MOVE");

	    //XFreePixmap(dpy,bitmap);
	    XMapWindow(dpy, newMes->win);
  
	    newMes->cs=cairo_xlib_surface_create(dpy, newMes->win, DefaultVisual(dpy, 0), 200, 100);
	    XMoveWindow(dpy,newMes->win,1000, CurretHeight);
	    XUnlockDisplay(dpy);
	    
	    newMes->Id = ++IDCounter;
	    pthread_mutex_lock(&mes_mut);
	    messages->push_back(newMes);
	    CurretHeight += gheight + 5;
	    pthread_mutex_unlock(&mes_mut);

	  };
	    
	  }	
	close(sock);
      }
    
}


int paint(Mess_t * ms)
{

  printf("Paint mess %d with tit - %s \n", ms->Id, ms->Title);
  
  cairo_t *cr = cairo_create(ms->cs);
  cairo_set_source_rgba (cr, 0, 0, 0, 1);
  cairo_paint(cr);
  //cairo_save(cr);
  cairo_set_source_rgba (cr, 1, 1, 0, 1);
  cairo_set_font_size (cr, 14);
  cairo_move_to (cr, 30, 20);
  //cairo_rotate(cr, PI / 2);
  cairo_show_text (cr, ms->Title);
  //cairo_restore(cr);

  cairo_set_source_rgba (cr, 0x2c, 0x2c, 0x2c, 1);
  cairo_set_font_size (cr, 14);
  cairo_move_to (cr, 20, 40);
  //cairo_rotate(cr, PI / 2);
  cairo_show_text (cr, ms->Message);
  //cairo_restore(cr);

  if (cairo_status(cr)) {
    printf("Cairo is unhappy: %s\n",
	   cairo_status_to_string(cairo_status(cr)));
    exit(0);
  }
  cairo_destroy(cr);
  cairo_surface_flush(ms->cs);
  return 100;
}

void
setAtomProperty(Display * dpy,
		Window &win,
		const char * at1,
		const char * at2,
		unsigned int bits)
{

  Atom atom1 = XInternAtom(dpy, at1, True);
  if (atom1 != None) {
    Atom atom2 = XInternAtom(dpy, at2, True);
    XChangeProperty(dpy, win, atom1, XA_ATOM, bits, PropModeReplace, (unsigned char *)&atom2, 1); 
  }
}

int
main (int argc, char *argv[])
{

  pthread_t sock_thread;
  messages = new std::vector<Mess_t*>();
  
  pthread_mutex_init( &mes_mut, NULL );
  XInitThreads();
  
  Display *dpy;
  XEvent e; 

  if(!(dpy=XOpenDisplay(NULL))) {
    fprintf(stderr, "ERROR: Could not open display\n");
    exit(1);
  }
  
  int posx = 1000;
  int posy = 100;
   
  int tret = pthread_create(&sock_thread, NULL, reciever, &dpy);
  
  
  std::vector<std::vector<Mess_t*>::iterator> itemsForRemove;
  std::time_t currentTime = std::time(NULL);
  bool needMoving = false;
  for(;;) 
    {
      pthread_mutex_lock(&mes_mut);
      XLockDisplay(dpy);
      int row = 0;//posy;
      currentTime = time(NULL);
      int index = 0;
      int moveFrom = -1;

      for(std::vector<Mess_t*>::iterator i = messages->begin();
	  i != messages->end();
	  i++,index++)
	{
	  
	  
	  //printf("dif = %u \n", ( (currentTime - (*i)->createdTime)));
	  if( ( (currentTime - (*i)->createdTime)) >= (*i)->Time  )
	    {
	      if(moveFrom == -1)
		{
		  moveFrom = index;
		} 
	      itemsForRemove.push_back(i);
	    }
	  else
	    {
	      paint((*i));
	      row += (*i)->height;

	      if(needMoving == true)
		{
		  XMoveWindow(dpy,(*i)->win,posx, row);
		}
	     
	      if(XCheckWindowEvent(dpy, (*i)->win, ExposureMask, &e) == true)
		{
		  //printf("Event\n");
		  if(e.type==Expose) 
		    {
		      
		      /*printf("Expose\n");
		      paint((*i));
		      row += (*i)->height;
		      XMoveWindow(dpy,(*i)->win,posx, row);*/
		    }
		};
	    }
	  printf("row - %d\n",row);
	}
      needMoving = false;
      if(!itemsForRemove.empty())
	{
	  printf("removing %d \n",itemsForRemove.size());
	  for(std::vector<std::vector<Mess_t*>::iterator>::reverse_iterator 
		i = itemsForRemove.rbegin();
	      i < itemsForRemove.rend();
	      i++)
	    {
	      Mess_t * ci = *(*i);
	      printf("Push to remove Id - %d \n", ci->Id);
	      CurretHeight -= ci->height;
	      setAtomProperty(dpy,ci->win,"_NET_WM_STATE", "_NET_WM_STATE_HIDEN");
	      cairo_surface_destroy(ci->cs);
	      //usleep(1000);
	      XUnmapWindow(dpy,ci->win);
	      XDestroyWindow(dpy,ci->win);
	      messages->erase(*i);
	      needMoving = true;
	    }
	  itemsForRemove.clear();
	}

      XFlush(dpy);
      XUnlockDisplay(dpy);
      pthread_mutex_unlock(&mes_mut);
      usleep(5000);

    }
  
  //cairo_surface_destroy(cs);
  XCloseDisplay(dpy);
      
  pthread_join(sock_thread, NULL);    

  return 0;
}
